﻿using System;
using System.Collections.Generic;
using System.Data.Odbc;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using KewlStuff.Extensions;
using log4net;
using MovieModel.Model;
using MovieModel.Model.Common;

namespace MovieBuddy.Helpers
{

    internal class MovieFileTracker
    {
        private readonly Manager manager;
        private readonly HashSet<string> usedMovieFiles = new HashSet<string>();
        private readonly Dictionary<string, Movie> movieFileNamesNoExtension = new Dictionary<string, Movie>();

        public MovieFileTracker(Manager manager)
        {
            this.manager = manager;
        }

        public void NotifyMovieExists(Movie movie)
        {
            FileInfo file = movie.MovieFile;
            usedMovieFiles.Add(file.FullName);
            movieFileNamesNoExtension[GetMovieNameNoExtension(file)] = movie;
        }

        internal static string GetMovieNameNoExtension(FileInfo file)
        {
            return file.Name.Remove(file.Name.Length - file.Extension.Length);
        }

        public void NotifyMovieRemoved(Movie movie)
        {
            foreach (var m in manager.Movies.Values)
            {
                if (m != movie && m.MovieFile.FullName == movie.MovieFile.FullName)
                {
                    // another movie is already using the movie file, so don't remove it from the list
                    return;
                }
            }
            FileInfo file = movie.MovieFile;
            usedMovieFiles.Remove(file.FullName);
            movieFileNamesNoExtension.Remove(GetMovieNameNoExtension(file));
        }

        /// <summary>
        /// Gets the movie related to this file or null if there is no movie
        /// </summary>
        /// <param name="file">the file in question</param>
        /// <returns>the movie whose movie file has the same name as file with with a different extension</returns>
        public Movie GetMovieRelatedToFile(FileInfo file)
        {
            string noExtensionName = GetMovieNameNoExtension(file);
            Movie movie;
            if (movieFileNamesNoExtension.TryGetValue(noExtensionName, out movie))
            {
                return movie;
            }
            return null;
        }

        public bool IsUsed(string fileName)
        {
            return usedMovieFiles.Contains(fileName);
        }
    }

    public class MovieDetector : IShutdownListener
    {
        private readonly Thread detectVideoThread;
        private readonly Manager manager;
        private readonly MovieFileTracker tracker;
        private readonly ILog Log = LogManager.GetLogger(typeof (MovieDetector));
        public string DetectVideoFolder { get; set; }
         

        private volatile bool shutdown = false;

            
        public MovieDetector(Manager manager)
        {
            this.manager = manager;
            tracker = new MovieFileTracker(manager);
            //TODO: when a movie is updated, need to update usedMovieFiles.
            foreach (var movie in manager.Movies.Values)
            {
                tracker.NotifyMovieExists(movie);
            }
            manager.MovieAdded += ManagerMovieAdded;
            manager.MovieRemoved +=ManagerMovieRemoved;
            detectVideoThread = new Thread(DetectVideos);
            detectVideoThread.Start();
        }

        private void ManagerMovieRemoved(object sender, ItemRemovedEventArgs<Movie> e)
        {
            tracker.NotifyMovieRemoved(e.RemovedItem);
        }

        void ManagerMovieAdded(object sender, ItemAddedEventArgs<Movie> e)
        {
            tracker.NotifyMovieExists(e.AddedItem);
        }

        internal string GetMovieTitleFromFile(FileInfo file)
        {
            return CleanMovieTitle(file.Name.Remove(file.Name.Length - file.Extension.Length));
        }

        public string CleanMovieTitle(string title)
        {
            // 1) turn underscores into spaces.  Underscores are stupid
            title = title.Replace('_', ' ');

            // 2) make words which are all caps not all caps anymore.
            //    I used regexes so if there is fancy caps they are left (like McClain)
            Regex[] regexes = {new Regex("^([A-Z])([A-Z:;]+)$"), new Regex("^([a-z])([a-z:;]+)$")};
            string[] words = title.Split(' ');
            for (int i = 0; i < words.Length; i++)
            {
                foreach (Regex regex in regexes)
                {
                    Match m = regex.Match(words[i]);
                    if (m.Success)
                    {
                        words[i] = words[i].Substring(0, 1).ToUpper() + words[i].Substring(1).ToLower();
                    }
                }
            }
            title = words.JoinString(" ");
            Log.Debug("CleanMovieTitle(" + title + ") returning [" + title + "]");
            return title;
        }

        private void DetectVideos()
        {
            while (!shutdown)
            {
                try
                {
                    if (!string.IsNullOrEmpty(DetectVideoFolder))
                    {
                        string folder = DetectVideoFolder;
                        Log.Debug("Starting detection pass on " + folder);
                        DetectionPass(folder);
                    }
                    Thread.Sleep(10000);
                }
                catch (ThreadInterruptedException)
                {
                    // expected on shutdown
                }
                catch (Exception e)
                {
                    Log.Error("Error on detection pass", e);
                }
            }
        }

        public void DetectionPass(string dir)
        {
            var files = Directory.EnumerateFiles(dir);

            foreach (FileInfo file in files.Select(f => new FileInfo(f)))
            {
                try
                {
                    if (string.IsNullOrEmpty(file.Extension)) continue; // fuck files without extensions really...
                    CheckAndHandleMovieFile(file);
                    CheckAndHandleDescriptionFile(file);
                    CheckAndHandleImageFile(file);
                    CheckAndHandleTagFile(file);
                }
                catch (Exception e)
                {
                    Log.Error("Error on file " + file, e);
                }
            }

            var subDirs = Directory.EnumerateDirectories(dir);
            foreach (var subDir in subDirs)
            {
                DirectoryInfo subDirInfo = new DirectoryInfo(subDir);
                if (subDirInfo.Name.ToLower() == "abcletters") continue; // abcletters is a stupid folder.
                DetectionPass(subDir);
            }
        }

        private void CheckAndHandleTagFile(FileInfo file)
        {
            if (file.Extension.ToLower() != ".tag") return;
            Movie movie = tracker.GetMovieRelatedToFile(file);
            if (movie == null) return;
            string fileTag = new DirectoryInfo(file.DirectoryName).Name;
            if (fileTag.ToLower() == "all") return; // "all" is a stupid tag
            //see if the movie already has this tag
            if (movie.Tags.Any(t => t.Name.ToLower() == fileTag.ToLower())) return;
            
            // need to get the id if the tag already exists.  Feels like the manager should do more for us here :/
            int? tagId = null;
            foreach (var tag in manager.Tags)
            {
                if (tag.Value.Name.ToLower() == fileTag.ToLower())
                {
                    tagId = tag.Key;
                    break;
                }
            }

            var readyTag = manager.GetAndAddTags(new[] {new Tuple<int?, string>(tagId, fileTag)});
            movie.Update(movie.Name, movie.ThumbnailLocation, movie.ImageLocation, movie.Year, movie.Runtime,
                movie.Description, movie.Tags.Concat(readyTag), movie.Companies, movie.People, movie.MovieFile);
        }

        private void CheckAndHandleImageFile(FileInfo file)
        {
            if (!MovieConstants.IsImageExtension(file.Extension)) return;
            Movie movie = tracker.GetMovieRelatedToFile(file);
            if (movie == null) return;
            if (!string.IsNullOrEmpty(movie.ImageLocation)) return;

            var thumbReq = new Manager.ThumbnailRequest(null, 61);
            string image = manager.ImportFile(new Uri(file.FullName), thumbReq).FullName;
            string thumb = thumbReq.File.FullName;

            movie.Update(movie.Name, thumb, image, movie.Year, movie.Runtime,
                movie.Description, movie.Tags, movie.Companies, movie.People, movie.MovieFile);
        }

        private void CheckAndHandleDescriptionFile(FileInfo file)
        {
            if (file.Extension.ToLower() != ".txt") return;
            Movie movie = tracker.GetMovieRelatedToFile(file);
            if (movie == null) return;
            if (!string.IsNullOrEmpty(movie.Description)) return;
            var lines = File.ReadAllLines(file.FullName).ToList();
            string name = lines[0];
            string description = string.Join("\n", lines.Skip(1));
            movie.Update(name, movie.ThumbnailLocation, movie.ImageLocation, movie.Year, movie.Runtime,
                description, movie.Tags, movie.Companies, movie.People, movie.MovieFile);
        }

        private void CheckAndHandleMovieFile(FileInfo file)
        {
            if (tracker.IsUsed(file.FullName)) return;
            if (string.IsNullOrEmpty(file.Extension)) return;
            // get the extension and strip off the period at the beginning
            if (MovieConstants.IsMovieExtension(file.Extension))
            {
                Movie movie = Movie.CreateNewMovie(CleanMovieTitle(file.Name).Substring(0, file.Name.LastIndexOf('.')));
                movie.Update(movie.Name, null, null, null, null, null, new Tag[0], new Company[0], new MoviePart[0], file);
                manager.AddMovie(movie);
            }
        }

        public void Shutdown()
        {
            shutdown = true;
            detectVideoThread.Interrupt();
        }
    }
}
